import json
import requests
import urllib.parse as urlparse
import time
import hashlib
import hmac
import sys

from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin2.api_client.common import (SyncQueryer)
from enum import Enum

class Side(Enum):
  BUY = "buy"
  SELL = "sell"

class OrderType(Enum):
  LIMIT = "limit"
  MARKET = "market"

class TimeInForce(Enum):
  GTC = "gtc"
  FOK = "fok"
  IOC = "ioc"

class BitAuth(requests.auth.AuthBase):
  def __init__(self, key):
    self._key = AuthKey.from_file(key)

  def encode_list(self, item_list):
    list_val = []
    for item in item_list:
      obj_val = self.encode_object(item)
      list_val.append(obj_val)
    output = '&'.join(list_val)
    output = '[' + output + ']'
    return output

  def encode_object(self, obj):
    if isinstance(obj, (str, int)):
      return obj

    # treat obj as dict
    sorted_keys = sorted(obj.keys())
    ret_list = []
    for key in sorted_keys:
      val = obj[key]
      if isinstance(val, list):
        list_val = self.encode_list(val)
        ret_list.append(f'{key}={list_val}')
      elif isinstance(val, dict):
        # call encode_object recursively
        dict_val = self.encode_object(val)
        ret_list.append(f'{key}={dict_val}')
      elif isinstance(val, bool):
        bool_val = str(val).lower()
        ret_list.append(f'{key}={bool_val}')
      else:
        general_val = str(val)
        ret_list.append(f'{key}={general_val}')

    sorted_list = sorted(ret_list)
    output = '&'.join(sorted_list)
    return output

  def _gen_signature(self, api_path, param_map):
    str_to_sign = api_path + '&' + self.encode_object(param_map)
    # print('str_to_sign = ' + str_to_sign)
    sig = hmac.new(self._key.secret_key_bytes, str_to_sign.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
    return sig

  def __call__(self, req: requests.PreparedRequest):
    assert req.method in ["GET", "POST"], "only support method GET and POST!"

    parsed = urlparse.urlparse(req.url)

    if req.method == "GET":
      query = dict(urlparse.parse_qsl(parsed.query))
    elif req.method == "POST":
      query = json.loads(req.body)
      query = {k: v for k, v in query.items() if v is not None}

    timestamp = int(time.time() * 1000)
    query["timestamp"] = timestamp

    sign = self._gen_signature(parsed.path, query)

    query["signature"] = sign

    if req.method == "GET":
      req.prepare_url(req.url, query)
    elif req.method == "POST":
      req.prepare_body(None, None, query)
      req.headers.update({"Content-Type": "application/json"})

    req.headers.update({"X-Bit-Access-Key": self._key.access_key})
    req.prepare_headers(req.headers)

    # print(req.headers)
    # print(req.body)
    # print(req.url)

    return req

class BitPrivateClient():
  def __init__(self, key_path: str, ip=None):
    self._auth = BitAuth(key_path)
    self._url = "https://api.bit.com"

    self._query = SyncQueryer(api_host=self._url, auth=self._auth, ip=ip)

  def get_open_orders(self, pair: str = None):
    method = "GET"
    params = {
      'pair': pair,
    }
    path = "/spot/v1/open_orders"

    response = self._query.query(method=method, path=path, params=params)
    return response.json()

  def cancel_order(self, order_id: str = None, pair: str = None, label: str = None):
    count = len(list(filter(lambda x : x != None, [order_id, pair, label])))
    assert count <= 1, "Can only provide one of: order_id/pair/label, or Empty filter means cancel all open order by this user"

    method = "POST"
    json = {
      'order_id': order_id,
      'pair': pair,
      'label': label,
    }
    path = "/spot/v1/cancel_orders"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

  def submit_order(
      self,
      pair: str,
      qty: str,
      quote_qty: str,
      side: Side,
      price: str = None,
      order_type: OrderType = None,
      time_in_force: TimeInForce = None,
      label: str = None,
      post_only: bool =  None,
    ):

    method = "POST"
    json = {
      'pair': pair,
      'qty': qty,
      'quote_qty': quote_qty,
      'side': side.value,
      'price': price,
      'order_type': order_type and order_type.value,
      'time_in_force': time_in_force and time_in_force.value,
      'label': label,
      'post_only': post_only,
      'reject_post_only': True,
      'self_trading_mode': 2,
    }
    path = "/spot/v1/orders"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

# api doc url
# https://www.bit.com/docs/en-us/spot.html#introduction

if __name__ == "__main__":
  key_path = sys.argv[1]
  
  bit = BitPrivateClient(key_path)
  info = bit.get_open_orders("BTC-USDT")
  # info = bit.cancel_order("1234")
  # info = bit.submit_order("BTC-USDT", "0.0001", "", Side.SELL, "1000000", OrderType.LIMIT, TimeInForce.GTC, "20230301tianqiangtest", True)
  print(json.dumps(info, indent=2))
